import { LinkedList, LinkedListNode } from "../common/linked_list";
import { Command, Location } from "../protocol/token.service";
import { AddElementLog, ElementLog, FocusElementLog, RemoveElementLog, undo, UpdateElementLog } from "./element-log";
import { ContextElement, InputElement, isSplitElement, RootElement, SchemaElement, SchemaKeyElement, MainElement, SchemaNextLineElement, SchemaSplitorElement, SchemaTypeElement, SchemaValueElement, STypeName, SimpleObjectElement, withLog, SimpleArrayElement } from "./schema-element";
import { appendElementLog, getElementLocation, getElementNode, getNextElement, getNextElementByNode, getTailElement, isFirstMainElement, isLastMainElement, logsToCommandList, nextElementTypeMatch, onlyOneChildMainElement } from "./schema-util";

/**
 * 处理结果
 */
export interface HandleResult {
    logs?: ElementLog[];
    msg?: string;
}

/**
 * 元素以及元素所在的位置和节点
 */
export interface ElementLocation<E extends MainElement> {
    element: E;                 // 元素
    location: Location;         // 元素所在的位置
    node: LinkedListNode<SchemaElement>;    // 元素所在的节点
}

/**
 * 处理结果转命令
 * @param result 处理结果
 * @returns 命令
 */
export const toCommandOrUndo = (result: HandleResult): Array<Command> => {
    if (result.msg) {
        if (result.logs) {
            undo(result.logs);
        }
        return [] as Command[];
    } else {
        if (!result.logs) {
            return [];
        }
        return logsToCommandList(result.logs);
    }
}

export const handleKeyInputComplete = ({ element, node }: ElementLocation<InputElement>, newKey: string): HandleResult => {
    // 更新当前元素
    const keyElement = element as SchemaKeyElement;
    const logs: ElementLog[] = [new UpdateElementLog(keyElement, keyElement.key)];
    keyElement.key = newKey;

    // 创建后续元素
    const nextElement = getNextElement(element);
    if ((!nextElement) || nextElement.type !== 'type') {
        const newNode = appendElementLog(logs, {
            type: 'type',
            typeName: '',
        } as SchemaTypeElement, element.parentElement!, node);
        logs.push(new FocusElementLog(newNode.value as InputElement));
    }
    return { logs };
}

export const defaultValueMap: Record<string, string> = {
    "string": '',
    "number": '0',
    "boolean": 'false',
    "null": 'null'
}

export const handleTypeInputComplete = ({ element, node }: ElementLocation<InputElement>, inputValue: string): HandleResult => {
    if (!['string', 'number', 'boolean', 'array', 'object', 'null'].includes(inputValue)) {
        return { msg: `Unsupported type: ${inputValue}` }
    }

    // 更新元素
    const typeElement = element as SchemaTypeElement;
    const logs: ElementLog[] = [new UpdateElementLog(typeElement, typeElement.typeName)];
    typeElement.typeName = inputValue as STypeName;

    // 创建后续元素
    if (!nextElementTypeMatch(element, ['value', 'schemaObject', 'schemaArray'])) {
        const defaultValue = defaultValueMap[inputValue];
        if (defaultValue !== undefined) {
            const newNode = appendElementLog(logs, {
                type: 'value',
                value: defaultValue,
            } as SchemaValueElement, element.parentElement!, node);
            logs.push(new FocusElementLog(newNode.value as InputElement));
        } else if (inputValue === 'array') {
            const arrayElement = new SimpleArrayElement();
            withLog(element.parentElement!, logs, () => element.parentElement!.insertChild(arrayElement, node));
            const typeElement: SchemaTypeElement = {
                type: 'type',
                typeName: ''
            }
            withLog(arrayElement, logs, () => arrayElement.insertChild(typeElement));
            logs.push(new FocusElementLog(typeElement));
        } else if (inputValue === 'object') {
            const objectElement = new SimpleObjectElement();
            withLog(element.parentElement!, logs, () => {
                element.parentElement!.insertChild(objectElement, node);
            });
            const keyNode = appendElementLog(logs, {
                type: 'key',
                key: ''
            } as SchemaKeyElement, objectElement);
            logs.push(new FocusElementLog(keyNode.value as InputElement));
        }
    }
    return { logs };
}

export const handleValueInputComplete = ({ element, node }: ElementLocation<InputElement>, inputValue: string): HandleResult => {
    const valueElement = element as SchemaValueElement;
    const logs: ElementLog[] = [new UpdateElementLog(valueElement, valueElement.value)];
    valueElement.value = inputValue;
    const parent = element.parentElement!;
    if (parent.type === 'root') {
        return { logs };
    }

    if (parent.type === 'schemaObject') {
        if (!nextElementTypeMatch(element, ['key'])) {
            const node1 = appendElementLog(logs, {
                type: 'splitor',
                splitor: ','
            } as SchemaSplitorElement, parent, node);
            const node2 = appendElementLog(logs, {
                type: 'key',
                key: ''
            } as SchemaKeyElement, parent, node1);
            logs.push(new FocusElementLog(node2.value as InputElement));
        }
    } else if (parent.type === 'schemaArray') {
        if (!nextElementTypeMatch(element, ['type'])) {
            const node1 = appendElementLog(logs, {
                type: 'splitor',
                splitor: ','
            } as SchemaSplitorElement, parent, node);
            const node2 = appendElementLog(logs, {
                type: 'type',
                typeName: ''
            } as SchemaTypeElement, parent, node1);
            logs.push(new FocusElementLog(node2.value as InputElement));
        }
    }
    return { logs };
}

/**
 * 处理完成上下文请求
 */
export const handleContextComplete = ({ element, node }: ElementLocation<MainElement>): HandleResult => {
    const logs: ElementLog[] = [];
    const parent = element.parentElement!;
    if (parent.type === 'root') {
        return { logs };
    } else if (parent.type === 'schemaObject') {
        if (!nextElementTypeMatch(element, ['key'])) {
            const node1 = appendElementLog(logs, {
                type: 'splitor',
                splitor: ','
            } as SchemaSplitorElement, parent, node);
            const node2 = appendElementLog(logs, {
                type: 'key',
                key: ''
            } as SchemaKeyElement, parent, node1);
            logs.push(new FocusElementLog(node2.value as InputElement));
        }
    } else if (parent.type === 'schemaArray') {
        if (!nextElementTypeMatch(element, ['type'])) {
            const node1 = appendElementLog(logs, {
                type: 'splitor',
                splitor: ','
            } as SchemaSplitorElement, parent, node);
            const node2 = appendElementLog(logs, {
                type: 'type',
                typeName: ''
            } as SchemaTypeElement, parent, node1);
            logs.push(new FocusElementLog(node2.value as InputElement));
        }
    }
    return { logs };
}

export const handleBlurElement = (node: LinkedListNode<SchemaElement>): HandleResult => {
    const element = node.value as InputElement;
    let data = '';
    if (element.type === 'key') {
        data = element.key;
    } else if (element.type === 'type') {
        data = element.typeName;
    } else if (element.type === 'value') {
        data = element.value;
    }
    if (data) {
        return { logs: [] };
    }

    const parent = element.parentElement!;
    if (parent.type === 'schemaArray') {
        const last = isLastMainElement(parent, node);
        if (!last) {
            return { logs: [] }
        }
        const first = isFirstMainElement(parent, node);
        if (first) {
            return { logs: [] };
        }
        if (element.type !== 'type') {
            return { logs: [] };
        }

        const logs: ElementLog[] = [];
        logs.push(new RemoveElementLog(node));
        
        const children = parent.children;
        children.removeNode(node);
        let prevNode = node.prev;
        while (prevNode && isSplitElement(prevNode.value)) {
            logs.push(new RemoveElementLog(prevNode));
            children.removeNode(prevNode);
            prevNode = prevNode.prev;
        }
        return { logs };
    } else if (parent.type === 'schemaObject') {
        const last = isLastMainElement(parent, node);
        if (!last) {
            return { logs: [] }
        }
        const first = isFirstMainElement(parent, node);
        if (first) {
            return { logs: [] };
        }
        if (element.type !== 'key') {
            return { logs: [] };
        }
        
        const logs: ElementLog[] = [];
        logs.push(new RemoveElementLog(node));
        const children = parent.children;
        children.removeNode(node);
        let prevNode = node.prev;
        while (prevNode && isSplitElement(prevNode.value)) {
            logs.push(new RemoveElementLog(prevNode));
            children.removeNode(prevNode);
            prevNode = prevNode.prev;
        }
        return { logs };
    }
    return { logs: [] };
}

export const handleDelete = ({ element, node }: ElementLocation<MainElement>): HandleResult => {
    const parent = element.parentElement!;
    if (parent.type === 'root' && isFirstMainElement(parent, node)) {
        return { msg: `root first element can't delete` };
    }

    const type = element.type;
    if (type === 'type') {
        const nextElement = getNextElementByNode(node);
        if (nextElement && nextElement.type === 'value') {
            return { msg: 'Exists value element when delete type element ' };
        }
    } else if (type === 'key') {
        // 判断是否是第一个key
        const first = isFirstMainElement(parent, node);
        if (first) {
            const nextElement = getNextElementByNode(node);
            if (nextElement && (nextElement.type === 'type' || nextElement.type === 'value')) {
                return { msg: "first key can't delete" };
            }
        }
    }

    let deleteParent = parent.type !== 'root' && onlyOneChildMainElement(parent);
    if (deleteParent) {
        const parentElement = parent as MainElement;
        return handleDelete({
            element: parentElement,
            node: getElementNode(parentElement),
            location: getElementLocation(parentElement)
        });
    }

    const logs: ElementLog[] = [];
    logs.push(new RemoveElementLog(node));
    
    parent.removeChildNode(node);
    let prevNode = node.prev;
    while (prevNode && isSplitElement(prevNode.value)) {
        logs.push(new RemoveElementLog(prevNode));
        parent.removeChildNode(prevNode);
        prevNode = prevNode.prev;
    }
    
    if (prevNode) {
        let element: MainElement | null = prevNode.value as MainElement;
        while (element) {
            const type = element.type;
            if (type === 'schemaArray' || type === 'schemaObject') {
                element = getTailElement(element as ContextElement);
            } else {
                logs.push(new FocusElementLog(element as InputElement));
                break;
            }
        }
    }
    return { logs }
}

/**
 * 处理换行请求
 */
export const handleNextLine = ({ element, node }: ElementLocation<MainElement>): HandleResult => {
    const logs: ElementLog[] = [];
    const parent = element.parentElement!;

    if (parent.type !== 'root') {
        const tail = parent.children.getTail();
        if (tail && tail.value.type !== 'nextLine') {
            const nextLineElement: SchemaNextLineElement = {
                type: 'nextLine',
                offset: 0,
                parentElement: element.parentElement
            };

            // TODO 改成insertNextLine
            logs.push(new AddElementLog(nextLineElement, children.insertAfter(nextLineElement)));
        }
    }

    const nextLineElement: SchemaNextLineElement = {
        type: 'nextLine',
        offset: 1,
        parentElement: element.parentElement
    };
    logs.push(new AddElementLog(nextLineElement, children.insertBefore(nextLineElement, node)));
    logs.push(new FocusElementLog(element as InputElement));
    return { logs };
}


    
/**
 * 计算缩进
 */
// function calculateIndent(element: SchemaElement): number {
//     const parent = element.parentElement!;
//     switch(element.type) {
//         case "key": 
//         case "type":
//         case "value": {
//             let currentNode = getElementNode(element as MainElement);
//             while (currentNode && currentNode.prev) {
//                 currentNode = currentNode.prev;
//                 if (currentNode.value.type === 'nextLine') {
//                     return (currentNode.value as SchemaNextLineElement).indent;
//                 }
//             }

//             if (parent.type !== 'root') {
//                 return calculateIndent(parent) + 1;
//             }
//             return 0;
//         }
//         case "schemaObject":
//         case "schemaArray": {
//             if (parent.type !== 'root') {
//                 return calculateIndent(parent) + 1;
//             }
//             return 0;
//         }
//         default:
//             return 0;
//     }
// }
